package main

import (
	"github.com/gin-gonic/gin"
	"github.com/spf13/viper"
	"github.com/thinkerou/favicon"
	"jxc/Common"
	"jxc/Controller"
	"net/http"
	"os"
	"strings"
)

func main() {

	//++++++++++++++++++++++++++++++++++++++++++
	//加载配置文件 之后可以通过viper.GetString("")获取到配置
	//
	//获取运行程序的当前目录
	workDir, _ := os.Getwd()
	//配置文件名称config
	viper.SetConfigName("config")
	//配置文件类型和后缀为yml
	viper.SetConfigType("yml")
	//加载当前目录下的config.yml
	viper.AddConfigPath(workDir)
	err := viper.ReadInConfig()
	if err != nil {
		panic("读取配置文件出错:" + err.Error())
	}
	//++++++++++++++++++++++++++++++++++++++++++

	//++++++++++++++++++++++++++++++++++++++++++
	//初始化数据库
	//
	Common.InitDB()
	//++++++++++++++++++++++++++++++++++++++++++

	//++++++++++++++++++++++++++++++++++++++++++
	//创建web服务器
	//
	//gin.SetMode(gin.ReleaseMode)   //切换到生产模式 此模式下无输出 默认是开发模式 有访问时有输出
	//初始化gin web服务器
	r := gin.Default()
	//使用cors中间件，作用是解决跨域等访问问题
	r.Use(CorsMiddleware())
	//加载网页标题小图标
	r.Use(favicon.New("./favicon.ico"))
	//加载web资源
	//r.LoadHTMLGlob("./web/*")
	//新建get方式路由 测试使用 返回200状态码和内容
	r.GET("/", func(ctx *gin.Context) { ctx.String(200, "Hello World!") })
	//新建post方式路由 访问目标为Controller文件夹下admin.go中Login()函数
	r.POST("/v1/login", Controller.Login) //login接口不需要token 并在登录后返回token
	//新建post方式路由 先使用Auth中间件用来校验token 之后访问UserInfo()函数
	r.POST("/v1/user_info", AuthMiddleware(), Controller.UserInfo)
	//读取配置文件config.yml中的端口号并开启web服务器 捕捉报错
	panic(r.Run(":" + viper.GetString("server.port")))
	//++++++++++++++++++++++++++++++++++++++++++
}

// Core中间件 解决跨越等问题
func CorsMiddleware() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		//允许的访问来源，*为任意来源
		ctx.Writer.Header().Set("Access-Control-Allow-Origin", "*")
		//预检请求的有效期，24小时，在客户端跨域请求时都会先发送预检请求，在有效期内不会二次发送，设置为0则每次请求都需要发送预检请求
		ctx.Writer.Header().Set("Access-Control-Max-Age", "86400")
		//允许的请求方式，只做接口可以只支持post
		ctx.Writer.Header().Set("Access-Control-Allow-Methods", "GET,POST")
		//允许的头部数据，常见的有Authorization，content-type等，*为任意数据，不做限制
		ctx.Writer.Header().Set("Access-Control-Allow-Headers", "*")
		//允许客户端请求中携带认证信息如cookie和http认证等
		ctx.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
		if ctx.Request.Method == http.MethodOptions { //如果是预检请求，直接返回200状态码表示服务器通
			ctx.AbortWithStatus(200)
		} else {
			ctx.Next()
		}
	}
}

// Auth中间件 用于鉴权
func AuthMiddleware() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		//从请求头中获取Authorization即之前登录时下发的令牌token
		tokenString := ctx.GetHeader("Authorization")
		//判断token非空 或者不是以Bearer 开头说明令牌格式有误或者令牌不存在
		if tokenString == "" || !strings.HasPrefix(tokenString, "Bearer ") {
			Common.RetMsg(0, "no Authorization", nil, ctx)
			ctx.Abort()
			return
		}
		//令牌去掉前7个前缀
		tokenString = tokenString[7:]
		//解析令牌 token中Valid会告知是否有效 claims中存放用户id和appid
		token, claims, err := Common.ParseToken(tokenString)
		if err != nil || !token.Valid {
			Common.RetMsg(0, "no permission", nil, ctx)
			//中断后续的接口调用
			ctx.Abort()
			return
		}
		//将用户id和appid存到上下文中，可以在后续的接口中调用
		ctx.Set("userid", claims.UserId)
		ctx.Set("app", claims.App)
		//继续后续的接口调用
		ctx.Next()
	}
}
